import * as _ from 'lodash'
import * as chokidar from 'chokidar'
import * as WebSocket from 'koa-websocket'
import * as send from 'koa-send'
import {IConfig,IServers} from './interface/IFace'
import { isUndefined } from 'util';
import Config from './middleware/Config';
const Koa = require('koa2')
const http = require('http');
const fs = require('fs')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const KoaBody = require('koa-body')
const Session = require('./lib/Session')
const debug = require('debug')('n2hws')
const app = WebSocket(new Koa());
var HttpServers:{}={};
//load Config File
module.exports = class Server {
    protected _listened: Array < number >= []
    protected _configDir: string;
    protected _config: IConfig;
    protected _reserve: {} = {};
    protected _env:string = process.env.NODE_ENV || 'development'
    protected _mimes: {} = {
        'css': 'text/css',
        'less': 'text/css',
        'gif': 'image/gif',
        'html': 'text/html',
        'ico': 'image/x-icon',
        'jpeg': 'image/jpeg',
        'jpg': 'image/jpeg',
        'js': 'text/javascript',
        'json': 'application/json',
        'pdf': 'application/pdf',
        'png': 'image/png',
        'svg': 'image/svg+xml',
        'swf': 'application/x-shockwave-flash',
        'tiff': 'image/tiff',
        'txt': 'text/plain',
        'wav': 'audio/x-wav',
        'wma': 'audio/x-ms-wma',
        'wmv': 'video/x-ms-wmv',
        'xml': 'text/xml'
    }
    protected _restart = 0;
    /**
     * 未完成的连接数
     */
    protected _unfinished: number = 0;
    /**
     * 加载配置文件
     * @param conf_path 
     */
    protected conf(conf_path: string = '') {
        if (conf_path === '') {
            conf_path = this._configDir + path.sep + 'n2hws.js'
        }
        try {
            var conf: IConfig = require(conf_path);
            for(let x in conf.servers){
                if(_.isObject(conf.servers[x].database[this._env]))
                    conf.servers[x].database = conf.servers[x].database[this._env]
                conf.servers[x].view_path = conf.servers[x].view_path || conf.servers[x].root+"View"
            }
            this._config = conf;
            var mimes = require(path.join(this._configDir, 'mimes'))
            if (_.isObject(mimes))
                this._mimes = Object.assign(this._mimes, mimes)
            if (this._restart == 0)
                chokidar.watch([conf_path]).on('change', () => {
                    console.log('检测到配置文件变更，正在准备重载应用配置.')
                    delete require.cache[conf_path];
                    this.restart();
                })
        } catch (e) {
            console.log(e)
        } finally{
            if(isUndefined(this._config.servers)||this._config.servers.length==0){
                console.log('配置文件错误，请检查配置文件是否正确')
                process.exit();   
            }
        }
    }
    public use() {
        app.use.apply(app, ...arguments)
    }
    public async isStatic() {
        return true;
    }
    public parseMime(url) {
        let extName = path.extname(url)
        extName = extName ? extName.slice(1) : 'unknown'
        return this._mimes[extName]
    }
    public walk(reqPath) {

        let files = fs.readdirSync(reqPath);

        let dirList = [],
            fileList = [];
        for (let i = 0, len = files.length; i < len; i++) {
            let item = files[i];
            let itemArr = item.split("\.");
            let itemMime = (itemArr.length > 1) ? itemArr[itemArr.length - 1] : "undefined";

            if (typeof this._mimes[itemMime] === "undefined") {
                dirList.push(files[i]);
            } else {
                fileList.push(files[i]);
            }
        }


        let result = dirList.concat(fileList);

        return result;
    };
    /**
     * 封装目录内容
     * @param  {string} url 当前请求的上下文中的url，即ctx.url
     * @param  {string} reqPath 请求静态资源的完整本地路径
     * @return {string} 返回目录内容，封装成HTML
     */
    public dir(url, reqPath) {

        // 遍历读取当前目录下的文件、子目录
        let contentList = this.walk(reqPath)

        let html = `<ul>`
        for (let [index, item] of contentList.entries()) {
            html = `${html}<li><a href="${url === '/' ? '' : url}/${item}">${item}</a>`
        }
        html = `${html}</ul>`

        return html
    }
    public content(fullStaticPath, url, config: IServers) {
        // 封装请求资源的完绝对径
        let reqPath = fullStaticPath

        // 判断请求路径是否为存在目录或者文件
        let exist = fs.existsSync(reqPath)

        // 返回请求内容， 默认为空
        let content = ''

        if (!exist) {
            //如果请求路径不存在，返回404
            content = '404 Not Found! o(╯□╰)o！'
        } else {
            //判断访问地址是文件夹还是文件
            let stat = fs.statSync(reqPath)

            if (stat.isDirectory() && config.autoindex) {
                //如果为目录，则渲读取目录内容
                content = this.dir(url, reqPath)

            } else {
                // 如果请求为文件，则读取文件内容
                content = this.file(reqPath)
            }
        }
        return content
    }
    public file(filePath) {
        let content = fs.readFileSync(filePath, 'binary')
        return content
    }

    public async route(ctx,next) {
        try {
            let host = ctx.host;
            let url = ctx.request.url;
            if (this._reserve[host]) {
                let config:IServers = this._reserve[host];
                let match = url.match(/\/([A-Za-z][A-Za-z0-9]{0,})\/([A-Za-z][A-Za-z0-9]{0,})/)
                let Controller:string='';
                let Method:string='';
                if (match !== null) {
                    Controller = match[1]
                    Method = match[2]
                }
                config.Controller = Controller;
                config.Method = Method;
                ctx.config = config;
                if(config.permission instanceof Function  && await config.permission(ctx,{Controller,Method},next)){
                    
                }
                //自动加载解码相关的函数逻辑
                if (config.decode instanceof Function) {
                    await config.decode.apply(ctx, [ctx])
                }
                if (ctx.method == "GET") {
                    let indexs:string|Array<string> = [];
                    if (url == '/') {
                        indexs = config.index;
                    } else {
                        indexs.push(url)
                    }
                    for (let index of indexs) {
                        let fullStaticPath = path.join(config.static, index)
                        //先判断是否是静态文件目录，
                        if (fs.existsSync(fullStaticPath)) {
                            let _content = ''
                            //判断访问地址是文件夹还是文件
                            let stat = fs.statSync(fullStaticPath)

                            if (stat.isDirectory() && config.autoindex) {
                                //如果为目录，则渲读取目录内容
                                _content = this.dir(url, fullStaticPath)

                            } else {
                                // 如果请求为文件，则读取文件内容
                                await send(ctx, index, {
                                    root: config.static
                                })
                                return;
                            }

                            // 解析请求内容的类型
                            let _mime = this.parseMime(ctx.url)

                            // 如果有对应的文件类型，就配置上下文的类型
                            if (_mime) {
                                ctx.type = _mime
                            }

                            // 输出静态资源内容
                            if (_mime && _mime.indexOf('image/') >= 0) {
                                // 如果是图片，则用node原生res，输出二进制数据
                                ctx.res.writeHead(200)
                                ctx.res.write(_content, 'binary')
                                ctx.res.end()
                            } else {
                                // 其他则输出文本
                                ctx.body = _content
                            }
                            await next();
                            return;
                        }
                    }
                }
                //解析url得到类名、方法名称等参数
                if (match !== null) {
                    let result = await this.controller(ctx, Controller, Method, config)

                    // 自动执行相关编码逻辑
                    if (config.encode instanceof Function) {
                        result = await config.encode.apply(ctx, [result, ctx])
                    }
                    ctx.body = result;
                } else {
                    this.response_404(ctx)
                }
            } else {
                this.response_404(ctx)
            }
            await next();
        } catch (e) {
            this.response_500(ctx, e)
        }
    }
    /**
     * 
     * @param ctx 
     * @param Controller 
     * @param Method 
     * @param config 
     */
    protected async controller(ctx: any, Controller: string, Method: string, config: IServers) {
        try {
            //获取项目目录
            let ProjectPath = config.root;
            //生成文件路径
            let ControllerPath = path.join(ProjectPath, `Controller/${Controller}Controller`)
            let ControllerClass = require(ControllerPath);
            if(ControllerClass[`${Controller}Controller`]){
                ControllerClass = ControllerClass[`${Controller}Controller`]
            }
            let c = new ControllerClass(ctx);
            let result = "";
            if (c[Method] instanceof Function) {
                if (c[`_before_${Method}`] instanceof Function) {
                    await c[`_before_${Method}`].apply(c, [ctx])
                }
                result = await c[Method].apply(c, [ctx.request.body])
                if (c[`_after_${Method}`] instanceof Function) {
                    result = await c[`_after_${Method}`].apply(c, [result, ctx])
                }
            }
            return result;
        } catch (e) {
            console.error(e.stack)
            return "Error";
        }
    }
    protected run() {
        // 重置配置信息
        this._reserve = {};
        var that = this;
        _.forOwn(this._config.servers, (v: any, k) => {
            if (v.listen && HttpServers[v.listen] == undefined) {
                HttpServers[v.listen] = http.createServer(app.callback()).listen(v.listen)
                this._listened.push(v._listened)
                console.log(`添加监听:${v.listen}`)
            }
            if (_.isUndefined(v.index)) {
                v.index = ['index.html']
            }
            if (_.isString(v.index)) {
                v.index = [v.index]
            }
            if (_.isArray(v.server_name)) {
                for (let server_name of v.server_name) {
                    that._reserve[`${server_name}:${v.listen}`] = v;
                }
            } else {
                that._reserve[`${v.server_name}:${v.listen}`] = v;
            }
            console.log(`listen ${v.server_name}:${v.listen}`)
        })
    }
    protected response_404(ctx) {
        ctx.status = 404;
        ctx.body = 404;
    }
    protected response_500(ctx, e) {
        ctx.status = 500;
        ctx.body = 500;
    }

    public start(dir: string) {        
        this._configDir = dir;
        app.use(KoaBody({multipart:true}))
        // app.use(bodyParser())
        app.use(Config)
        app.use(async (ctx,next) => {
            this._unfinished++;
            await this.route(ctx,next)
            this._unfinished--;
        })
        this.conf()
        this.run()        
    }
    public restart() {
        console.log('正在重载应用')
        this.conf()
        this.run()        
    }
}